'use strict';

exports.__esModule = true;

var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };

exports["default"] = connect;

var _react = require('react');

var _storeShape = require('../utils/storeShape');

var _storeShape2 = _interopRequireDefault(_storeShape);

var _shallowEqual = require('../utils/shallowEqual');

var _shallowEqual2 = _interopRequireDefault(_shallowEqual);

var _wrapActionCreators = require('../utils/wrapActionCreators');

var _wrapActionCreators2 = _interopRequireDefault(_wrapActionCreators);

var _warning = require('../utils/warning');

var _warning2 = _interopRequireDefault(_warning);

var _isPlainObject = require('lodash/isPlainObject');

var _isPlainObject2 = _interopRequireDefault(_isPlainObject);

var _hoistNonReactStatics = require('../utils/hoist-non-react-statics');

var _hoistNonReactStatics2 = _interopRequireDefault(_hoistNonReactStatics);

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }

function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }

var defaultMapStateToProps = function defaultMapStateToProps(state) {
    return {};
}; // eslint-disable-line no-unused-vars
var defaultMapDispatchToProps = function defaultMapDispatchToProps(dispatch) {
    return { dispatch: dispatch };
};
var defaultMergeProps = function defaultMergeProps(stateProps, dispatchProps, parentProps) {
    return _extends({}, parentProps, stateProps, dispatchProps);
};

function getDisplayName(WrappedComponent) {
    return WrappedComponent.displayName || WrappedComponent.name || 'Component';
}

var errorObject = { value: null };
function tryCatch(fn, ctx) {
    try {
        return fn.apply(ctx);
    } catch (e) {
        errorObject.value = e;
        return errorObject;
    }
}

// Helps track hot reloading.
var nextVersion = 0;

function connect(mapStateToProps, mapDispatchToProps, mergeProps) {
    var options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};

    var shouldSubscribe = Boolean(mapStateToProps);
    var mapState = mapStateToProps || defaultMapStateToProps;

    var mapDispatch = void 0;
    if (typeof mapDispatchToProps === 'function') {
        mapDispatch = mapDispatchToProps;
    } else if (!mapDispatchToProps) {
        mapDispatch = defaultMapDispatchToProps;
    } else {
        mapDispatch = (0, _wrapActionCreators2["default"])(mapDispatchToProps);
    }

    var finalMergeProps = mergeProps || defaultMergeProps;
    var _options$pure = options.pure,
        pure = _options$pure === undefined ? true : _options$pure,
        _options$withRef = options.withRef,
        withRef = _options$withRef === undefined ? false : _options$withRef;

    var checkMergedEquals = pure && finalMergeProps !== defaultMergeProps;

    // Helps track hot reloading.
    var version = nextVersion++;

    return function wrapWithConnect(WrappedComponent) {
        var connectDisplayName = 'Connect(' + getDisplayName(WrappedComponent) + ')';

        function checkStateShape(props, methodName) {
            if (!(0, _isPlainObject2["default"])(props)) {
                (0, _warning2["default"])(methodName + '() in ' + connectDisplayName + ' must return a plain object. ' + ('Instead received ' + props + '.'));
            }
        }

        function computeMergedProps(stateProps, dispatchProps, parentProps) {
            var mergedProps = finalMergeProps(stateProps, dispatchProps, parentProps);
            if (process.env.NODE_ENV !== 'production') {
                checkStateShape(mergedProps, 'mergeProps');
            }
            return mergedProps;
        }

        var Connect = function (_Component) {
            _inherits(Connect, _Component);

            Connect.prototype.shouldComponentUpdate = function shouldComponentUpdate() {
                return !pure || this.haveOwnPropsChanged || this.hasStoreStateChanged;
            };

            function Connect(props, context) {
                _classCallCheck(this, Connect);

                var _this = _possibleConstructorReturn(this, _Component.call(this, props, context));

                _this.version = version;
                _this.store = props.store || context.store;

                if (!_this.store) {
                    var str = 'Could not find "store" in either the context or ' + ('props of "' + connectDisplayName + '". ') + 'Either wrap the root component in a <Provider>, ' + ('or explicitly pass "store" as a prop to "' + connectDisplayName + '".');
                    console.log(str);
                }

                var storeState = _this.store.getState();
                _this.state = { storeState: storeState };
                _this.clearCache();
                return _this;
            }

            Connect.prototype.computeStateProps = function computeStateProps(store, props) {
                if (!this.finalMapStateToProps) {
                    return this.configureFinalMapState(store, props);
                }

                var state = store.getState();
                var stateProps = this.doStatePropsDependOnOwnProps ? this.finalMapStateToProps(state, props) : this.finalMapStateToProps(state);

                if (process.env.NODE_ENV !== 'production') {
                    checkStateShape(stateProps, 'mapStateToProps');
                }
                return stateProps;
            };

            Connect.prototype.configureFinalMapState = function configureFinalMapState(store, props) {
                var mappedState = mapState(store.getState(), props);
                var isFactory = typeof mappedState === 'function';

                this.finalMapStateToProps = isFactory ? mappedState : mapState;
                this.doStatePropsDependOnOwnProps = this.finalMapStateToProps.length !== 1;

                if (isFactory) {
                    return this.computeStateProps(store, props);
                }

                if (process.env.NODE_ENV !== 'production') {
                    checkStateShape(mappedState, 'mapStateToProps');
                }
                return mappedState;
            };

            Connect.prototype.computeDispatchProps = function computeDispatchProps(store, props) {
                if (!this.finalMapDispatchToProps) {
                    return this.configureFinalMapDispatch(store, props);
                }

                var dispatch = store.dispatch;

                var dispatchProps = this.doDispatchPropsDependOnOwnProps ? this.finalMapDispatchToProps(dispatch, props) : this.finalMapDispatchToProps(dispatch);

                if (process.env.NODE_ENV !== 'production') {
                    checkStateShape(dispatchProps, 'mapDispatchToProps');
                }
                return dispatchProps;
            };

            Connect.prototype.configureFinalMapDispatch = function configureFinalMapDispatch(store, props) {
                var mappedDispatch = mapDispatch(store.dispatch, props);
                var isFactory = typeof mappedDispatch === 'function';

                this.finalMapDispatchToProps = isFactory ? mappedDispatch : mapDispatch;
                this.doDispatchPropsDependOnOwnProps = this.finalMapDispatchToProps.length !== 1;

                if (isFactory) {
                    return this.computeDispatchProps(store, props);
                }

                if (process.env.NODE_ENV !== 'production') {
                    checkStateShape(mappedDispatch, 'mapDispatchToProps');
                }
                return mappedDispatch;
            };

            Connect.prototype.updateStatePropsIfNeeded = function updateStatePropsIfNeeded() {
                var nextStateProps = this.computeStateProps(this.store, this.props);
                if (this.stateProps && (0, _shallowEqual2["default"])(nextStateProps, this.stateProps)) {
                    return false;
                }

                this.stateProps = nextStateProps;
                return true;
            };

            Connect.prototype.updateDispatchPropsIfNeeded = function updateDispatchPropsIfNeeded() {
                var nextDispatchProps = this.computeDispatchProps(this.store, this.props);
                if (this.dispatchProps && (0, _shallowEqual2["default"])(nextDispatchProps, this.dispatchProps)) {
                    return false;
                }

                this.dispatchProps = nextDispatchProps;
                return true;
            };

            Connect.prototype.updateMergedPropsIfNeeded = function updateMergedPropsIfNeeded() {
                var nextMergedProps = computeMergedProps(this.stateProps, this.dispatchProps, this.props);
                if (this.mergedProps && checkMergedEquals && (0, _shallowEqual2["default"])(nextMergedProps, this.mergedProps)) {
                    return false;
                }

                this.mergedProps = nextMergedProps;
                return true;
            };

            Connect.prototype.isSubscribed = function isSubscribed() {
                return typeof this.unsubscribe === 'function';
            };

            Connect.prototype.trySubscribe = function trySubscribe() {
                if (shouldSubscribe && !this.unsubscribe) {
                    this.unsubscribe = this.store.subscribe(this.handleChange.bind(this));
                    this.handleChange();
                }
            };

            Connect.prototype.tryUnsubscribe = function tryUnsubscribe() {
                if (this.unsubscribe) {
                    this.unsubscribe();
                    this.unsubscribe = null;
                }
            };

            Connect.prototype.componentDidMount = function componentDidMount() {
                this.trySubscribe();
            };

            Connect.prototype.componentWillReceiveProps = function componentWillReceiveProps(nextProps) {
                if (!pure || !(0, _shallowEqual2["default"])(nextProps, this.props)) {
                    this.haveOwnPropsChanged = true;
                }
            };

            Connect.prototype.componentWillUnmount = function componentWillUnmount() {
                this.tryUnsubscribe();
                this.clearCache();
            };

            Connect.prototype.clearCache = function clearCache() {
                this.dispatchProps = null;
                this.stateProps = null;
                this.mergedProps = null;
                this.haveOwnPropsChanged = true;
                this.hasStoreStateChanged = true;
                this.haveStatePropsBeenPrecalculated = false;
                this.statePropsPrecalculationError = null;
                this.renderedElement = null;
                this.finalMapDispatchToProps = null;
                this.finalMapStateToProps = null;
            };

            Connect.prototype.handleChange = function handleChange() {
                if (!this.unsubscribe) {
                    return;
                }

                var storeState = this.store.getState();
                var prevStoreState = this.state.storeState;
                if (pure && prevStoreState === storeState) {
                    return;
                }

                if (pure && !this.doStatePropsDependOnOwnProps) {
                    var haveStatePropsChanged = tryCatch(this.updateStatePropsIfNeeded, this);
                    if (!haveStatePropsChanged) {
                        return;
                    }
                    if (haveStatePropsChanged === errorObject) {
                        this.statePropsPrecalculationError = errorObject.value;
                    }
                    this.haveStatePropsBeenPrecalculated = true;
                }

                this.hasStoreStateChanged = true;
                this.setState({ storeState: storeState });
            };

            Connect.prototype.getWrappedInstance = function getWrappedInstance() {
                return this.refs.wrappedInstance;
            };

            Connect.prototype.render = function render() {
                var haveOwnPropsChanged = this.haveOwnPropsChanged,
                    hasStoreStateChanged = this.hasStoreStateChanged,
                    haveStatePropsBeenPrecalculated = this.haveStatePropsBeenPrecalculated,
                    statePropsPrecalculationError = this.statePropsPrecalculationError,
                    renderedElement = this.renderedElement;


                this.haveOwnPropsChanged = false;
                this.hasStoreStateChanged = false;
                this.haveStatePropsBeenPrecalculated = false;
                this.statePropsPrecalculationError = null;

                if (statePropsPrecalculationError) {
                    throw statePropsPrecalculationError;
                }

                var shouldUpdateStateProps = true;
                var shouldUpdateDispatchProps = true;
                if (pure && renderedElement) {
                    shouldUpdateStateProps = hasStoreStateChanged || haveOwnPropsChanged && this.doStatePropsDependOnOwnProps;
                    shouldUpdateDispatchProps = haveOwnPropsChanged && this.doDispatchPropsDependOnOwnProps;
                }

                var haveStatePropsChanged = false;
                var haveDispatchPropsChanged = false;
                if (haveStatePropsBeenPrecalculated) {
                    haveStatePropsChanged = true;
                } else if (shouldUpdateStateProps) {
                    haveStatePropsChanged = this.updateStatePropsIfNeeded();
                }
                if (shouldUpdateDispatchProps) {
                    haveDispatchPropsChanged = this.updateDispatchPropsIfNeeded();
                }

                var haveMergedPropsChanged = true;
                if (haveStatePropsChanged || haveDispatchPropsChanged || haveOwnPropsChanged) {
                    haveMergedPropsChanged = this.updateMergedPropsIfNeeded();
                } else {
                    haveMergedPropsChanged = false;
                }

                if (!haveMergedPropsChanged && renderedElement) {
                    return renderedElement;
                }

                if (withRef) {
                    this.renderedElement = (0, _react.createElement)(WrappedComponent, _extends({}, this.mergedProps, {
                        ref: 'wrappedInstance'
                    }));
                } else {
                    this.renderedElement = (0, _react.createElement)(WrappedComponent, this.mergedProps);
                }

                return this.renderedElement;
            };

            return Connect;
        }(_react.Component);

        Connect.displayName = connectDisplayName;
        Connect.WrappedComponent = WrappedComponent;
        Connect.contextTypes = {
            store: _storeShape2["default"]
        };
        Connect.propTypes = {
            store: _storeShape2["default"]
        };

        if (process.env.NODE_ENV !== 'production') {
            Connect.prototype.componentWillUpdate = function componentWillUpdate() {
                if (this.version === version) {
                    return;
                }

                // We are hot reloading!
                this.version = version;
                this.trySubscribe();
                this.clearCache();
            };
        }

        return (0, _hoistNonReactStatics2["default"])(Connect, WrappedComponent);
    };
}